use std::sync::Arc;

use tauri::Window;
use tokio::{io::{AsyncWriteExt, BufReader}, sync::Semaphore};
use tokio_serial::{SerialPortBuilderExt, SerialStream};

use crate::{
    error::{AppErr, IoErr},
    serial_port::proto::read,
    utils::get_mut,
};

const SERIAL_PORT_EMIT_EVENT: &'static str = "SERIAL_PORT_EMIT";

unsafe impl Send for SerialPort {}
unsafe impl Sync for SerialPort {}

pub struct SerialPort {
    stream: SerialStream,
    exit_sem: Semaphore,
    app_win: Window,
}

pub type SharedPort = Arc<SerialPort>;

impl SerialPort {

    pub fn new(app_win: Window, name: &str, baud_rate: u32) -> Result<SharedPort, IoErr> {
        let stream = tokio_serial::new(name, baud_rate).open_native_async()?;
        let port = SerialPort {
            stream,
            exit_sem: Semaphore::new(0),
            app_win,
        };
        let port = Arc::new(port);
        tokio::spawn( read_task(port.clone()) );
        Ok(port)
    }

    pub async fn write(&self, buf: &[u8]) -> Result<(), IoErr> {
        get_mut(&self.stream).write_all(buf).await?;
        Ok(())
    }

    pub fn close(&self) {
        self.exit_sem.add_permits(1);
    }
}

async fn read_task(port: SharedPort) {
    let mut reader = BufReader::with_capacity(128, get_mut(&port.stream));

    loop {
        let buf = tokio::select! {

            buf = read(&mut reader) => buf,

            _ = port.exit_sem.acquire() => {
                break;
            }
        };

        let buf = match buf {
            Ok(buf) => buf,
            Err(AppErr::Proto(_)) => continue,
            Err(_) => break,
        };
        _ = port.app_win.emit(SERIAL_PORT_EMIT_EVENT, buf);
    }

    println!("serial port read task exit");
}
